package container

import (
	"context"
	"sync"
	"sync/atomic"
	"time"

	"gitee.com/doraemon1996/bunny/goroutine"
	"gitee.com/doraemon1996/bunny/log"
	"gitee.com/doraemon1996/bunny/structure"
)

// 容器管理器
type manager struct {
	structure.Control

	// 所有运行中的容器
	listMu sync.RWMutex
	list   map[uint64]*container

	// 服务别名 可以多个别名指向同一个容器
	regMu    sync.RWMutex
	registry map[string]*container

	sequence atomic.Uint64 // 自增容器序号
	nodeID   uint64        // 节点ID
}

// 创建容器管理器对象
func NewManager() Manager {
	return new(manager)
}

func (mng *manager) Init(ctx context.Context, nodeID uint64) {
	mng.Control.Init(ctx, func() {
		mng.list = make(map[uint64]*container)
		mng.registry = make(map[string]*container)
		mng.sequence.Store(0)
		mng.nodeID = nodeID
	})
}

func (mng *manager) Start() {
	mng.Control.Start(func() {
		mng.Add(1)
		goroutine.Submit(func() {
			defer mng.Done()
			mng.activeCheck()
		})
	})
}

func (mng *manager) Stop() {
	mng.Control.Stop(func() {
		mng.Wait()

		mng.listMu.Lock()
		list := mng.list
		mng.list = nil
		mng.listMu.Unlock()

		mng.regMu.Lock()
		mng.registry = nil
		mng.regMu.Unlock()

		for _, v := range list {
			ctn := v // 循环内闭包引用问题规避
			goroutine.Submit(func() {
				ctn.Stop()
			})
		}
	})
}

func (mng *manager) activeCheck() {
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	list := make([]*container, 0)

	for {
		select {
		case <-mng.CtxDone():
			return
		case <-ticker.C:
			mng.listMu.RLock()
			for _, ctn := range mng.list {
				list = append(list, ctn)
			}
			mng.listMu.RUnlock()

			now := time.Now()
			for i := 0; i < len(list); i++ {
				if !list[i].ActiveCheck(now) {
					log.Warnf("%v maybe endloop!!!", list[i])
				}
				list[i] = nil
			}
			list = list[:0]
		}
	}
}

// 启动一个容器
func (mng *manager) Launch(name string, srv Service, args ...any) (containerID uint64, err error) {
	if mng.Closed() {
		err = ErrManagerClosed
		return
	}

	if srv == nil {
		err = ErrInvalidService
		return
	}

	ctnID := ContainerIDEncode(mng.nodeID, mng.sequence.Add(1))

	ctn := new(container)
	err = ctn.Init(mng.Context(), ctnID, srv, args...)
	if err != nil {
		return
	}

	err = ctn.Start()
	if err != nil {
		return
	}

	// 调用服务的start()方法可能耗时很久，再次检测状态
	if mng.Closed() {
		err = ErrManagerClosed
		return
	}

	mng.listMu.Lock()
	mng.list[ctnID] = ctn
	mng.listMu.Unlock()

	mng.regMu.Lock()
	mng.registry[name] = ctn
	mng.regMu.Unlock()

	containerID = ctnID

	return
}

// 注册容器别名
func (mng *manager) Register(containerID uint64, name string) (err error) {
	if mng.Closed() {
		return ErrManagerClosed
	}

	mng.listMu.RLock()
	ctn, ok := mng.list[containerID]
	mng.listMu.RUnlock()

	if !ok {
		return ErrContainerNotFound
	}

	mng.regMu.Lock()
	mng.registry[name] = ctn
	mng.regMu.Unlock()

	return
}

// 关闭指定容器
func (mng *manager) Kill(killArg any) {
	if mng.Closed() {
		return
	}

	var ctnID uint64
	switch val := killArg.(type) {
	case uint64:
		ctnID = val
	case string:
		mng.regMu.RLock()
		ctn, exist := mng.registry[val]
		if exist {
			ctnID = ctn.ContainerID()
		}
		mng.regMu.RUnlock()
	default:
		return
	}

	mng.listMu.Lock()
	ctn, exist := mng.list[ctnID]
	if exist {
		delete(mng.list, ctnID)
	}
	mng.listMu.Unlock()

	if !exist {
		return
	}

	list := make([]string, 0)
	mng.regMu.Lock()
	for name, v := range mng.registry {
		if v.ContainerID() == ctnID {
			list = append(list, name)
		}
	}
	for _, name := range list {
		delete(mng.registry, name)
	}
	mng.regMu.Unlock()

	// 异步关闭容器
	// 无法查询到容器，即代表容器已关闭
	goroutine.Submit(func() {
		ctn.Stop()
	})
}

// 获取所有已注册的服务别名
func (mng *manager) List() (list map[string]uint64) {
	if mng.Closed() {
		return
	}

	mng.regMu.RLock()
	defer mng.regMu.RUnlock()

	list = make(map[string]uint64)
	for name, ctn := range mng.registry {
		list[name] = ctn.ContainerID()
	}

	return
}

// 根据容器名字获取容器ID
func (mng *manager) Query(name string) (containerID uint64, exist bool) {
	if mng.Closed() {
		return
	}

	mng.regMu.RLock()
	defer mng.regMu.RUnlock()

	ctn, exist := mng.registry[name]
	if exist {
		containerID = ctn.ContainerID()
	}

	return
}

// 派发消息
func (mng *manager) PutMessage(containerID uint64, msg Message) {
	if mng.Closed() {
		log.Errorf("PutMessage but manager closed. msg: %s", msg)
		msg.Error(ErrManagerClosed)
		return
	}

	mng.listMu.RLock()
	ctn, ok := mng.list[containerID]
	mng.listMu.RUnlock()

	if !ok {
		log.Errorf("PutMessage but contaier not found. msg: %s", msg)
		msg.Error(ErrContainerNotFound)
		return
	}

	ctn.PutMessage(msg)
}
